# coding:utf-8
import re
import os
from port_parse.port_lexer import lexer
from port_parse.expression import expr_cal

class portParse():
    def __init__(self,paramparse = 0,incdir=[]):
        self.lexer = lexer()
        self.lexer.build()
        self.incdir = incdir

        # paramparse is 1: the parameter of port will change to actual width which is number.
        self.paramparse = paramparse
        self.matched = 0
        # portname : [<porttype>,<[range1]>,<[ranage2]>]
        # port_type : input,output or interface name
        # range1 : array type, packed array
        # range2 : array type, unpacked array
        # exampile:  input logic signe [3:0][7:0] data [7:0] ,
        #          port type            range1         range2
        self.portname  = []
        self.portsInfo = {}
        # if parameter name start with "1" ,it is localparam , otherwise it is parameter
        self.paramInfo = {}
        
        self.action_port = {
                -1             : {"MODULE" : "k_module"} ,
                "k_module"     : {"ID"     : 'v_modulename'} ,
                "v_modulename" : {"#"      : "k_paradef#","LPAREN" : "k_portlslparen"} ,

                "k_paradef#"     : {"LPAREN":"k_paralparen"} ,
                "k_paralparen"   : {"PARAMETER":"k_parameter","RPAREN":"k_pararparen"} ,
                "k_parameter"    : {"ID":"v_paraname"} ,
                "v_paraname"     : {"=":"k_paraeq","COMMA":"k_parasplit","RPAREN":"k_pararparen"} ,
                "k_paraeq"       : {"COMMA":"k_parasplit","RPAREN":"k_pararparen"} ,
                "k_parasplit"    : {"ID":"v_paraname","PARAMETER":"k_parameter"} ,
                "k_pararparen"   : {"LPAREN":"k_portlslparen"} ,
                "k_portlslparen" : {"INPUT":"k_portdir","OUTPUT":"k_portdir","ID":"w_infdefportname","INF":"v_infdef"},

                "k_portdir"     : {"LOGIC":"k_nettype","WIRE":"k_nettype","REG":"k_nettype",'SIGNED':"k_signflag","UNSIGNED":"k_signflag","LBRAC":"k_rangelbrac","ID":"v_portname"} ,
                "k_nettype"     : {"SIGNED":"k_signflag","UNSIGNED":"k_signflag","LBRAC":"k_rangelbrac","ID":"v_portname"} ,
                "k_signflag"    : {"LBRAC":"k_rangelbrac","ID":"v_portname"} ,
                "k_rangelbrac"  : {"LBRAC":"k_rangelbrac","ID":"v_portname"} ,
                "v_portname"    : {"LBRAC":"k_nrangelbrac","COMMA":"k_portsplit","RPAREN":0} , # k_nrangelbrac为非压缩的数组定义，即变量名后的维度
                "k_nrangelbrac" : {"LBRAC":"k_nrangelbrac","COMMA":"k_portsplit"} ,
                "k_portsplit"   : {"INPUT":"k_portdir","OUTPUT":"k_portdir","ID":"w_portname_infdef","INF":"v_infdef"} ,
                "w_portname_infdef":{ "ID":"v_portname", "COMMA":"k_portsplit" ,"LBRAC":"k_nrangelbrac","RPAREN":0} ,
                "v_infdef"      : {"ID":"v_portname"},
                "w_infdefportname" : {"COMMA":"k_unansportsplit","ID":"v_portname"} ,
                "k_unansportsplit" : {"ID":"v_unansportname"},
                "v_unansportname" : {"COMMA":"k_unansportsplit","RPAREN":0}
                }

        self.action_code = {
                # k_function skip until ENDFUNCTION
                # k_task skip until ENDTASK
                # k_generate skip until ENDGENERATE
                # k_assign skip until ;
                # other example always block is not process now.
                -1: {"LOCALPARAM":"k_localparam","PARAMETER":"k_parameter","INPUT":"k_portdir","OUTPUT":"k_portdir","ID":"w_infdef","INF":"v_infdef","FUNCTION":"k_function","TASK":"k_task" ,"GENERATE":"k_generate","ASSIGN":"k_assign","ENDMODULE":0,"CASE":"k_case"} , 
                "k_parameter" :{"ID":"v_paraname"} ,
                "k_localparam":{"ID":"v_paraname"} ,
                "v_paraname":{"=":"k_paraeq"} , # need defalut is not define mode ???
                "k_paraeq":{"COMMA":"k_parasplit","SEM":-1} ,
                "k_parasplit":{"ID":"v_paraname"} ,

                "k_portdir": {"LOGIC":"k_nettype","WIRE":"k_nettype","REG":"k_nettype",'SIGNED':"k_signflag","UNSIGNED":"k_signflag","LBRAC":"k_rangelbrac","ID":"v_portname"} ,
                "k_nettype"     : {"SIGNED":"k_signflag","UNSIGNED":"k_signflag","LBRAC":"k_rangelbrac","ID":"v_portname"} ,
                "k_signflag"    : {"LBRAC":"k_rangelbrac","ID":"v_portname"} ,
                "k_rangelbrac"  : {"LBRAC":"k_rangelbrac","ID":"v_portname"} ,
                "v_portname"    : {"LBRAC":"k_nrangelbrac","COMMA":"k_portsplit","SEM":-1} , # k_nrangelbrac为非压缩的数组定义，即变量名后的维度
                "k_nrangelbrac" : {"LBRAC":"k_nrangelbrac","COMMA":"k_portsplit","SEM":-1} ,
                "k_portsplit"   : {"ID":"v_portname"} ,

                "w_infdef" : {"ID":"w_infname","#":"k_instance#"} , # other is return to -1 , k_instance# skip  until ";" and state return -1 
                "w_infname": {"COMMA":"k_portsplit","LPAREN":"k_instance(","SEM":"k_infdefend"} , # k_instance( skip until ";" and state return -1

                "v_infdef" : {"ID":"v_portname"}

                }

    def __portInfopost__(self):
        expr = expr_cal()
        expr.parasync(self.paramInfo)
        for port in self.portsInfo:
            ranges = []
            for rge in self.portsInfo[port][1]:
                (upper,lower) = rge.split(":")
                upper = expr.run(upper)
                lower = expr.run(lower)
                ranges.append((upper,lower))
            self.portsInfo[port][1] = ranges

            ranges = []
            for rge in self.portsInfo[port][2]:
                (upper,lower) = rge.split(":")
                upper = expr.run(upper)
                lower = expr.run(lower)
                ranges.append((upper,lower))
            self.portsInfo[port][2] = ranges

    def run(self,text,module=""):
        """
        if module is empty,the function will return all module info.
        if module is not empty,only return the info of module.
        """
        lexer_l = self.lexer
        include_flag = 0 # current code in include file or not
        x1 = None
        x1_vld = 0
        # delete the comment such as /*  */
        text = re.sub(r"/\*.*?\*/","",text,flags=re.S|re.M)
        lexer_l.input(text)
        expr = expr_cal()

        self.matched = 0
        while True:
            last_st = -1
            code_type = 0 # 0: ansi port define   1: unansi port define
            paren_level = 0 # paren level 
            location = 2 # 0:parameter list  1: port  list  2: don't know
            port_type = "input"
            net_type = "logic"
            sign_type = 0 # 0:无符号定义  "signed" or "unsigned":对应符号定义
            last_portname_param = ""
            range_common = []

            self.portname = []
            self.portsInfo = {}
            self.paramInfo = {}

            expr.parasync(self.paramInfo)
            module_vld = 0 # there are module define or not

            while True:
                tok = lexer_l.token()
                if not tok or tok.type=="MODULE":
                    if tok:
                        lexer_l.lexer.back(6) #back to module
                    break

            while True: #端口处理# {{{
                tok = lexer_l.token() # when last_st ==0 , tok.value is ;
                if not tok or last_st == 0:
                    # If the current stack contains valid data, return the stack.
                    if x1_vld and last_st != 0:
                        lexer_l = x1
                        x1_vld = 0
                        tok = lexer_l.token()
                    else:
                        break
                
                if(tok.type == "IFDEF"):
                    tok = lexer_l.token()
                    continue
                elif(tok.type == "ENDIF"):
                    continue
                elif(tok.type == "INCLUDE"):
                    tok = lexer_l.token() # remove "
                    file_include = ""
                    while(True):
                        tok = lexer_l.token()
                        file_include+=tok.value
                        if(tok.value=="\""):
                            x1 = lexer_l # save the current state
                            x1_vld = 1
                            if "lexer_inc" not in locals(): # If the lexer is not defined, you need to perform the initialization and define it.
                                lexer_inc = lexer()
                                lexer_inc.build()
                            inc_exists = 0
                            for i in self.incdir:
                                if(os.path.exists(i+"/"+file_include[:-1])):
                                    f_inc = open(i+"/"+file_include[:-1],"r")
                                    inc_exists = 1
                                    break
                            if inc_exists:
                                lexer_l = lexer_inc
                                text = f_inc.read()
                                text = re.sub(r"/\*.*?\*/","",text,flags=re.S|re.M)
                                lexer_l.input(text)
                                tok = lexer_l.token()
                                break
                            else:
                                print("error can't find the include file "+file_include[:-1]+"! please specify the incdir.")
                                tok = lexer_l.token()
                                break
                cur_st = self.action_port.get(last_st,{}).get(tok.type,"error")
                if(cur_st == "error"):
                    print("error "+last_st+"  "+tok.value)
                if(location==0): # parameter define process

                    if(cur_st == "v_paraname"):
                        #print(tok.value) #此处的tok.value就是parameter名
                        last_portname_param = tok.value
                        self.paramInfo[tok.value] = ""
                    elif(cur_st == "k_paraeq"):
                        paravalue = ""
                        while tok.type not in self.action_port["k_paraeq"] or paren_level != 0: #跳过parameter值表达式部分，直到分隔符
                            tok = lexer_l.token()
                            # When a ')' appears in the parameter list, 
                            # the state machine assumes it is the closing ')' of the parameter list, 
                            # causing it to osexit the parameter processing state.
                            # However, in reality, it is just a ')' within the parameter expression. 
                            # Therefore, ')' should be treated as a special case.
                            # When the last ')' (the outermost closing parenthesis) appears, 
                            # it should also cause the while loop to exit.
                            # Therefore, the token type at that point should be set to empty (or any other value that is not defined in the next state's parameters).
                            if(tok.value == "("):
                                paren_level += 1
                            elif(tok.value == ")"):
                                if(paren_level > 0):
                                    paren_level -= 1
                                    if(paren_level == 0):
                                        tok.type = ""
                            paravalue += tok.value
                        if True:
                            self.paramInfo[last_portname_param] = expr.run(paravalue[:-1])
                        else:
                            self.paramInfo[last_portname_param] = paravalue[:-1]

                        lexer_l.lexer.back(1)
                    elif(cur_st == "k_pararparen"):
                        location = 1

                elif(location==1): # port define process
                    if(cur_st == "k_portdir" or cur_st == "v_infdef"):
                        port_type = tok.value
                        net_type = "logic"
                        sign_type = 0
                        range_common = []
                    elif(cur_st == "k_nettype"):
                        net_type = tok.value
                    elif(cur_st == "k_signflag"):
                        sign_type = tok.value
                    elif(cur_st == "k_rangelbrac"):
                        range_common.append("")
                        while(tok.value != ']'):
                            tok = lexer_l.token()
                            if(tok.value != "]"):
                                range_common[-1]+=tok.value
                    elif(cur_st == "k_nrangelbrac"):
                        range_packed = ""
                        while(tok.value != ']'):
                            tok = lexer_l.token()
                            range_packed += tok.value
                            #if(tok.value != "]"):
                                #range_common[-1]+=tok.value
                        if(last_st == "w_portname_infdef"):
                            self.portsInfo[last_portname_param] = [port_type,range_common,[range_packed[:-1]]] # new port
                            self.portname.append(last_portname_param)
                        else:
                            self.portsInfo[last_portname_param][2].append(range_packed[:-1])
                    elif(cur_st=="v_portname"):
                        last_portname_param = tok.value
                        self.portsInfo[tok.value] = [port_type,range_common,[]] # new port
                        self.portname.append(tok.value)
                    elif(cur_st == "w_portname_infdef"):
                        last_portname_param = tok.value
                    elif(cur_st == "k_portsplit" or cur_st == 0):
                        if(last_st == "w_portname_infdef"):
                            self.portsInfo[last_portname_param] = [port_type,range_common,[]] # new port
                            self.portname.append(last_portname_param)
                    elif(cur_st == "v_unansportname"):
                        self.portsInfo[tok.value] = [None,[],[]] # unansi new port
                        self.portname.append(tok.value)
                    elif(cur_st == "k_unansportsplit"):
                        if(last_st == "w_infdefportname"):
                            code_type = 1  # the port is unansi type
                            self.portsInfo[last_portname_param] = [None,[],[]] # unansi new port
                            self.portname.append(last_portname_param)
                    elif(cur_st == "w_infdefportname"):# only first word in portls need check
                        last_portname_param = tok.value
                    #elif(last_st == "w_portname_infdef"):
                        #if(cur_st == "v_portname"):
                            #port_type = last_portname_param
                            #net_type  = "logic"
                            #sign_type = 0
                            #range_common = []
                            #print(tok.value) # 端口名

                else: # don't konw location，根据状态判断后续是参数定义还是端口定义
                    if(last_st== "v_modulename" and cur_st == "k_paradef#"):
                        location = 0
                    elif(last_st== "v_modulename" and cur_st == "k_portlslparen"):
                        location = 1
                    elif(cur_st == "v_modulename"):
                        if len(module)!=0:
                            if(tok.value!=module):
                                cur_st = 0  # cur_st is 0,the port process is skip
                                code_type = 0 # skip the code process.

                                # skip the all that in module ... endmodule block
                                while True: 
                                    tok = lexer_l.token() 
                                    if not tok or tok.value=="endmodule":
                                        # the loop will read a additial token,so there back to endmodule
                                        lexer_l.lexer.back(9)
                                        break
                            else:
                                self.matched = 1
                                module_vld = 1
                        else:
                            module_vld = 1
                            # print(tok.value) # tok.value is module name

                last_st = cur_st# }}}

            last_st = -1
            paren_level = 0

            closed_group = {"k_function":"ENDFUNCTION","k_task":"ENDTASK","k_generate":"ENDGENERATE","k_case":"ENDCASE"}
            while code_type: # port type is unansi need parse code part.
                if last_st!=0:
                    tok = lexer_l.token() # when last_st ==0 , tok.value is ;
                    if not tok:
                        break
                else:
                    break

                if(tok.type == "IFDEF"):
                    tok = lexer_l.token()
                    continue
                elif(tok.type == "ENDIF"):
                    continue
                elif(tok.type == "INCLUDE"):
                    tok = lexer_l.token()
                    while(True):
                        tok = lexer_l.token()
                        if(tok.value=="\""):
                            break
                    continue
                cur_st = self.action_code.get(last_st,{}).get(tok.type,"error")

                if(cur_st == "error"):
                    # return to initial state
                    last_st = -1
                    continue
                
                if(cur_st == "k_parameter"):
                    paratype = ""
                elif(cur_st == "k_localparam"):
                    paratype = "1"
                elif(cur_st == "v_paraname"):
                    last_portname_param = paratype+tok.value
                elif(cur_st == "k_paraeq"):
                    paravalue = ""
                    while tok.type not in self.action_code["k_paraeq"]: #跳过parameter值表达式部分，直到分隔符
                        tok = lexer_l.token()
                        paravalue += tok.value
                    lexer_l.lexer.back(1)
                    if self.paramparse:
                        self.paramInfo[last_portname_param] = expr.run(paravalue[:-1])
                    else:
                        self.paramInfo[last_portname_param] = paravalue[:-1]
                elif(cur_st == "k_portdir"):
                   port_type = tok.value
                   range_common = [] 
                elif(cur_st == "k_rangelbrac"):
                    range_common.append("")
                    while True:
                        tok = lexer_l.token()
                        if(tok.value == "]"):
                            break
                        range_common[-1]+=tok.value
                elif(cur_st == "v_portname"):
                    last_portname_param = tok.value
                    if(tok.value in self.portname):
                        self.portsInfo[tok.value] = [port_type,range_common,[]] # new port
                elif(cur_st == "k_nrangelbrac"):
                    range_packed = ""
                    while(tok.value != ']'):
                        tok = lexer_l.token()
                        range_packed += tok.value
                    if(last_portname_param in self.portname):
                        self.portsInfo[last_portname_param][2].append(range_packed[:-1])
                elif(cur_st == "w_infdef"):
                    port_type = tok.value
                    range_common = []
                elif(cur_st == "w_infname"):
                    last_portname_param = tok.value
                elif(cur_st == "k_portsplit"): # last state is w_infname
                    if(last_st == "w_infname"):
                        if(last_portname_param in self.portname):
                            self.portsInfo[last_portname_param] = [port_type,[],[]]
                elif(cur_st == "k_infdefend"):
                    if(last_portname_param in self.portname):
                        self.portsInfo[last_portname_param] = [port_type,[],[]]
                    cur_st = -1 # return initial state
                elif(cur_st == "k_function" or cur_st == "k_task" or cur_st == "k_generate" or cur_st == 'k_case'):
                    while True:
                        tok = lexer_l.token()
                        if(tok.type == closed_group[cur_st]):
                            break
                    cur_st = -1 # return initial state
                elif(cur_st == "k_assign" or cur_st == "k_instance#" or cur_st == "k_instance("):
                    while True:
                        tok = lexer_l.token()
                        if(tok.value == ";"):
                            break
                    cur_st = -1 # return initial state
                last_st = cur_st

            if module_vld:
                # if special the module ,find the module,then exit the function
                if len(module)!=0:
                    if self.paramparse:
                        self.__portInfopost__()
                    break
                else:
                    pass
                    #print(self.portsInfo)
            if not tok:
                break
    

    def getPortsInfo(self):
        return self.portsInfo

    def getPortName(self):
        return self.portname

    def getParams(self):
        return self.paramInfo


if __name__ == "__main__":
    
    import glob
    files = glob.glob("a.v")
    t = portParse()
    for i in files:
        with open(i,"r") as f:
            print("jsz "+i)
            cont = f.read()
            t.run(cont,"async_fifo")
            print(t.portsInfo)

